package zdream.control.world.buff;

import zdream.control.world.Box;
import zdream.control.world.MFoe;
import zdream.control.world.MFoeBuff;
import zdream.control.world.MHookType;
import zdream.control.world.MHooks;
import zdream.control.world.TerrainHandler;
import zdream.control.world.Ticker;

/**
 * 辅助跳跃计算的模块, 由 JumpHandler 转变而来
 * 
 * @author Zdream
 * @since 0.0.1
 * @date 2021-08-26 (created)
 * @date 2022-05-31 (last modified)
 */
public class JumpModule extends MFoeBuff {
	/**
	 * 如果角色的跳跃有更加复杂的逻辑, 不想主动调用 tick 触发, 设置 passive=true 即可.
	 */
	public boolean passive = false;

	public JumpModule(MFoe foe) {
		super(foe);
	}

	@Override
	public String getCategory() {
		return "jump";
	}

	@Override
	public void init() {
		onHook(MHookType.TICK)
				.handle(this::tick)
				.withPriority(-5)
				.buildAndSave();
	}

	public void tick(MHooks.MHookContext rawContext) {
		if (passive) {
			return;
		}

		Box box = foe;
		terrainHandler.glitchFix(box.flush());
		terrainHandler.freshBox(box);
		this.drop(foe.world.ticker);
		terrainHandler.submitMotion(box, 0, this.jumpDy);
	}

	/* **********
	 *   参数   *
	 ********** */
	/**
	 * 默认跳跃加速度 (当重力速度向下), 也就是每秒速度衰减值
	 */
	public static final float GRAVITY_ACCELERATION = -62.625f;
	/**
	 * 下落时最快速度 (当重力速度向下), 单位: p/s^2
	 * 每秒 28 块
	 */
	public static final float MAX_DROP_VELOCITY = -28;

	/**
	 * 如果目标是跳三格高, 向上初速度就是它. 每秒 >20 块
	 */
	public static final float JUMP_IMPULSE_3 = 20.2f;
	/**
	 * 如果目标是跳二格高, 向上初速度就是它. 每秒 16.5 块
	 */
	public static final float JUMP_IMPULSE_2 = 16.5f;
	/**
	 * 如果目标是跳四格高, 向上初速度就是它. 每秒 23 块
	 */
	public static final float JUMP_IMPULSE_4 = 23;

	/*
	 * 跳跃参数 (配置值)
	 */
	/**
	 * 跳跃向上的初始速度 (当重力速度向下), 配置值. 单位: p/s.
	 */
	public float impulse = JUMP_IMPULSE_3;
	/**
	 * 跳跃向上速度的衰减值 (当重力速度向下), 配置值. 单位: p/s^2
	 */
	public float decay = GRAVITY_ACCELERATION;

	/*
	 * 跳跃参数 (状态值)
	 */
	/**
	 * 本帧是否起跳
	 */
	public boolean jumpStart;
	/**
	 * 本帧是否结束起跳
	 */
	public boolean jumpEnd;
	/**
	 * 当前跳跃导致的纵向速度, 单位 p/s
	 * 原来是 jumpVel
	 */
	public float jumpVy;
	/**
	 * 当前跳跃导致的纵向位移, 单位 p
	 */
	public float jumpDy;
	/**
	 * <p>jumpHalt = 0 表示无滞空
	 * jumpHalt > 0 表示在滞空状态, 值域 [0, 50]
	 * <p>当跳跃键放开或者跳跃磕脑袋之后, 角色会有一段时间 (0.05s) 处于 vy = 0 的状态
	 * <p>重力向下且 gravityScale = 1 时, jumpVel 在 0.05 倍 decay 以上时进入滞空时,
	 *     有 0.05 秒滞空, jumpHalt = 50;
	 * 重力向下且 gravityScale = 1 时, jumpVel 在 0.05 倍 decay 以内时进入滞空时,
	 *     滞空时间 jumpHalt 为 (jumpVel) / (-decay)
	 * 重力向下且 gravityScale = 1 时, jumpVel 为负数, 无滞空时间
	 * </p>
	 */
	public byte jumpHalt = 0;

	public TerrainHandler terrainHandler;

	/* **********
	 * 实际计算 *
	 ********** */
	/*
	 * 在执行前应该需要执行这些:
	 * tHandler.glitchFix(box);
	 * tHandler.freshBox(box);
	 *
	 * 执行后应该执行这些:
	 * box.setVelocityY(jumpVel);
	 * foe.submitMotion();
	 */

	public void resetJumpVel() {
		jumpVy = 0;
		jumpStart = false;
		jumpHalt = 0;
		jumpDy = 0;
	}

	public void resetJumpVelFromImpulse() {
		jumpVy = (foe.gravityDown) ? impulse : -impulse;
		jumpStart = false;
	}

	/**
	 * 执行跳跃或自由落体. 当落地则不动了
	 * 结果请看 jumpVy 和 jumpDy.
	 */
	public void drop(Ticker ticker) {
		if (!foe.inAir && !jumpStart) {
			resetJumpVel();
			jumpEnd = false;
			return;
		}

		jumpStart = false;
		doJump(ticker);
		jumpEnd = false;
	}

	private void doJump(Ticker ticker) {
		// 还在滞空状态, 持续滞空
		if (jumpHalt > ticker.lDuration) {
			jumpVy = 0;
			jumpHalt -= ticker.lDuration;
			jumpDy = 0;
			return;
		}

		// 自由落体
		Box box = foe;
		float deltaVy = decay * box.gravityScale * ticker.duration;
		float maxDropVy = box.gravityVelocity * box.gravityScale;
		float lastVy = jumpVy;

		if (jumpHalt > 0) { // 只有一段时间在滞空, 其余时间坠落
			jumpVy += deltaVy * (1 - (float) jumpHalt / ticker.lDuration);
			jumpHalt = 0;
			jumpDy = (1 - (float) jumpHalt / ticker.lDuration) * ticker.duration * deltaVy / 2;
			return;
		}

		// 下面必定 jumpHalt == 0
		assert jumpHalt == 0;
		jumpVy += deltaVy;

		if (box.gravityDown) {
			// 是否磕脑袋
			if ((box.topTouched || jumpEnd) && jumpVy > 0 && box.gravityScale > 0) {
				float threshold = -decay * box.gravityScale * 0.05f;
				jumpHalt = (byte) Math.min(jumpVy / threshold * 1000, 50);
				jumpVy = 0;
				jumpDy = 0;
				return;
			}
			if (jumpVy < maxDropVy) {
				jumpVy = maxDropVy;
			}
		} else { // 重力方向向上
			// 是否磕脑袋
			if ((box.bottomTouched || jumpEnd) && jumpVy < 0 && box.gravityScale > 0) {
				// TODO 没测试过
				float threshold = -decay * box.gravityScale * 0.05f;
				jumpHalt = (byte) Math.min(jumpVy / threshold * 1000, 50);
				jumpVy = 0;
				jumpDy = 0;
				return;
			}
			if (jumpVy > maxDropVy) {
				jumpVy = maxDropVy;
			}
		}

		// 计算位移
		jumpDy = (lastVy + jumpVy) * ticker.duration / 2;
	}

	/**
	 * 跳跃. 它不判断你是否在空中, 能跳就是二段跳.
	 * 如果要计算位移还要额外调用一次 {@link #drop} 方法
	 */
	public void jump() {
		// 自由落体
		jumpStart = true;
		// 跳
		jumpVy = (foe.gravityDown) ? impulse : -impulse;
	}

	/**
	 * 模拟跳跃停止, 就像按跳跃键, 突然松开时
	 */
	public void releaseJump() {
		jumpEnd = true;
	}

	public static class JumpModuleFactory {
		JumpModule module;
		public JumpModuleFactory(MFoe foe) {
			module = new JumpModule(foe);
		}
		public JumpModuleFactory setJumpMaxDy(int y) {
			switch (y) {
			case 0: module.impulse = 0; break; // 不起跳, 垂直落下来
			case 2: module.impulse = JUMP_IMPULSE_2; break;
			case 3: module.impulse = JUMP_IMPULSE_3; break;
			case 4: module.impulse = JUMP_IMPULSE_4; break;
			default: throw new IllegalArgumentException("错误的 jump max dy: " + y);
			}
			return this;
		}
		public JumpModuleFactory defineAsPassiveMode() {
			module.passive = true;
			return this;
		}
		public JumpModuleFactory setTerrainHandler(TerrainHandler terrainHandler) {
			module.terrainHandler = terrainHandler;
			return this;
		}
		public JumpModule create() {
			if (!module.passive && module.terrainHandler == null) {
				throw new IllegalArgumentException("未设置被动模式时, terrainHandler 是必需的");
			}
			module.foe.addBuff(module);
			return module;
		}
	}
}
